/**
 *    Media Capture and Stream API 通常也称作 Media Stream API 或者简单称为 MediaStream API。
 * 它与提供媒体流处理能力的 WebRTC API 关系密切。它提供了很多接口和方法来处理流和构成流的轨，
 * 提供了与数据格式相关的约束，提供了异步获取数据时成功与失败的回调，以及处理过程中的其他事件回调。
 * 
 *    简单点说就是允许开发者通过 javascript 访问本地多媒体设备，并获取媒体流进行处理。
 * 
 *    MCS API 比较复杂，涉及到的接口很多，在深入学习前对所有接口有一个全局的把握很重要，所以先来看看本规格中两大基础概念和使用场景。
 * 一个 MediaStream 由零个或多个 MediaStreamTrack 对象组成，表示数个音频或视频轨道。
 * 每个 MediaStreamTrack 有一个或多个 channels。每个 cnannel 代表了这个媒体流的最小单元，例如，与给定扬声器关联的音频信号，如立体声音轨中的左右声道。
 * 
 *    MediaStream 对象只有一个输入一个输出。MediaStream 对象往往通过本地调用 getUserMedia() 产生，
 * 并且它的输入源一定是用户的麦克风或者摄像头其中之一。一个非本地的媒体流也许表示一个媒体元素，比如<video>和<audio>标签；
 * 也可以表示一个通过 WebRTC API 获得的源自网络的流；亦或是通过 Web Audio API 的 MediaStreamAudioSourceNode 创建的流。
 * MediaStream 对象的输入连接着一个消费者，这个消费者可以是媒体元素，可以是 WebRTC RTCPeerConnection API，
 * 也可以是 Web Audio API 中的 MediaStreamAudioDestinationNode 对象。
 */

/**
 *    MediaStream API 的主体由两部分构成，分别是 MediaStreamTrack 接口和 MediaStream 接口。
 * MediaStreamTrack 实例标识一个单一类型的媒体对象，来源于 UA 提供的一个媒体源，比如一个网络摄像头生产的视频，或者一个麦克风产生的音频。
 * MediaStream 接口则用于将多个 MediaStreamTrack 实例整合成一个可以在媒体元素上播放或录制的对象。
 * 
 *    每个 MediaStream 实例可以包含0个或多个 MediaStreamTrack 对象。MediaStream 内所有轨道在渲染时要求同步。
 * 这不是硬性要求，因为对于那些不同时钟的轨道来说，同步几乎不可能。此外，不同的 MediaStream 对象也不必同步。
 * 
 *    单独一个 MediaStreamTrack 对象代表了多个通道内容，比如立体声、立体声视频等，这样的通道之间拥有良好定义关系的内容。
 * 关于通道的信息由另外的 API （比如：WebAudio API）提供，这篇规格不直接提供访问通道的方法。
 * 
 *    MediaStream 对象将其下所有轨道结合起来，生成唯一的输入和唯一的输出。输出控制着这个 MediaStream 如何渲染，
 * 控制着录制时保存的内容或者应用于 video 元素时要展示的画面。一个 MediaStream 对象可以同时附加到多个不同的输出上。
 * 
 *    MediaStream 对象可以使用 MediaStream() 构造函数创建，创建时给出一个已存在的媒体流或者轨道。
 * 使用媒体流时，这个媒体流对象内的所有轨道也会被添加到新建的 MediaStrem 对象上，如果使用轨道，应该是一个 MediaStreamTrack 数组。
 *  后一种方法，使得将来自不同的源的流组成同一流成为可能。
 * 
 *    MediaStream 和 MediaStreamTrack 对象都可以被克隆。一个克隆的 MediaStream 包含原来对象中所有轨道成员。
 * 一个克隆的 MediaStreamTrack 有一个约束集合，这个约束集合依赖原始轨道对线确定，这个原始轨道对象允许来自同一源的媒体可以有不同的约束且可以应用到不同消费者。
 * MediaStream 对象也用于 getUserMedia 的外部上下文。
 */

// 下面开始就是数量众多的接口了
/**
 * MediaStream 构造函数接受一个可选的 MediaStream 实例或者 MediaStreamTrack 实例数组。
 *    MediaStream([ MediaStream straem [, Array<MediaStreamTrack> tracks]]);
 * 不论使用哪种参数，关联的 tracks 都保存在轨道集合（track set）中，关于保存的顺序没有强制要求，如果要获取指定轨道，往往通过轨道 id 实现。
 *    MediaStream 字典：{
 *        id: {string}，只读，当新建 MediaStream 对象时，UA 必须生成一个位移的字符串标识符，作为 id 属性值。
 *        active: {boolean}，只读，标识 MediaStrem 实例的有效性。对于一个 MedisStream 实例，如果其内轨道至少有一个未处于结束（ended）状态，则该实例有效（active）否则无效（inactive）
 *        onaddtrack: {EventHandler}，当已添加方式修改实例的轨道集合（track set）时触发
 *        onremovetrack: {EventHandler}，当已删除方式修改实例的轨道集合时触发。
 *        getAudioTracks: {Array<MediaStreamTrack> Function()}，返回轨道数组，表示这个流中所有音频轨道。
 *        getVideoTracks: {Array<MediaStreamTrack> Function()}，返回轨道数组，表示这个流中所有视频轨道。
 *        getTracks: {Array<MediaStreamTrack> Function()}，返回轨道数组，表示这个流中所有的轨道。
 *        getTrackById: {MediaStreamTrack Function(string id)}，返回 null，或者与指定 id 匹配的 MediaStreamTrack 对象。
 *        addTrack: {void Function(MediaStreamTrack track)}，添加指定的轨道对象，到当前流的轨道集合中
 *        removeTrack: {void Function(MediaStreamTrack track)}，从当前流的轨道集合中删除指定轨道对象
 *        clone: {MediaStream Function()}，克隆当前流及其下所有轨道。
 *    }
 * 
 * 
 * MediaStreamTrack 实例代表了一个 UA 中的媒体源。比如一个与 UA 连接的设备。其他规格可能为 MediaStreamTrack 定义了源，并重写了这里定义的行为。
 * MediaStreamTrack 对象不必要有规范的二进制格式，这就允许 UA 使用任何方法来操作媒体，只要这个方法适合于当前用户平台。
 * MediaStreamTrack 对象调用 stop 方法，可以让该对象不再需要它的源。当流中所有轨道使用某些方法 stoped 或者 ended 时，源也就 stoped 了。
 * MediaStreamTrack 对象有两个重要的概念：生命周期（Life-cycle）和媒体流动（Media Flow）
 *    每个轨道对象有两个生命周期状态：live（存活）和ended（结束），这取决于创建该轨道的源的状态，并通过 readyState 属性访问当前状态。
 * 在 live 状态下，该轨道和媒体可以被消费者消费（但当轨道静音muted或者禁用disabled时，会出现空信息）。
 * 消音或禁用的轨道会渲染出静音的音频或者黑屏的视频，也可能是等价的空信息内容。
 * 如果轨道的源是通过 getUserMedia 方法暴露出来的设备，那么当轨道状态变为静音或者禁用时，所有连接到该设备上的轨道都会静音或禁用。
 * 此时，UA 会将 [devicesLiveMap] 中对应的 [deviceId] 键的值设为 false，并且会尽可能快的重新设回 true，前提是有轨道的状态变为非静音或者重新启用。
 * 静音/非静音的轨道状态反应的是当前时刻源是否提供媒体数据，启用/禁用的状态则用于应用控制和决定轨道是否输出媒体数据。因此只有当轨道静音且可用时才媒体数据才会流动。
 *    对一个 live 状态下的轨道有两组特性：静音/非静音，启用/禁用。
 * 静音针对轨道的输入信息，如果实时采样不可用，则轨道静音。应用程序无法控制静音与否，但可以通过 muted 属性访问，也可以监听相关事件 mute 和 unmute 事件。（常见静音场景：关闭麦克风、操作系统上静音按钮、浏览器的静音按钮）
 * 通过轨道对象的 enabled 属性，应用程序可以控制 启用/禁用 状态。
 *    MediaStreamTrack 是一个可约束的对象，它的约束是一个定义在轨道上可以影响源的集合。不论是初始化轨道时提供约束，还是运行时确定约束，都定义在 ConstrainablePattern 接口上。
 * 该接口允许检索和操作一个轨道上当前确定的约束。如果 overconstrained 事件被抛出，那么轨道必须静音，直到一个新的可满足的约束对象被应用，或者一个已存在的约束对象变为可满足。
 *    MediaStreamTrack 字典：{
 *        kind: {string}，只读，返回媒体轨道的类型，如果是音频轨返回 audio，如果是视频轨返回 video
 *        id: {string}，只读，当对象创建时，UA 会根据算法生成一个身份标识符，并在初始化时赋值给 id 属性
 *        label: {string}，只读，UA 可以标注音频和视频源。如果标注存在，访问 label 属性会返回这个标注。如果不存在 label 则范湖空字符串
 *        enabled: {boolean}，enabled 属性用于控制对象的 启用/禁用 状态。获取时该属性返回最近设置的值
 *        muted: {boolean}，只读，当轨道静音时返回 true，否则返回 false
 *        onmute: {EventHandler}，轨道静音时触发的事件
 *        onunmute: {EventHandler}，轨道非静音时触发的事件
 *        readyState: {MediaStreamTrackState}，只读，代表了轨道的当前状态，返回 UA 最近设置的状态值，可用值只有 live 和 ended
 *        onended: {EventHandler}，当轨道流动结束时触发的事件
 *        onoverconstrained: {EventHandler}，轨道静音，直到一个新的可满足的约束对象被应用，或者一个已存在的约束对象变为可满足时触发的事件
 *        clone: {MediaStreamTrack Function()}，返回一个当前轨道对象的克隆
 *        stop: {void Function()}，结束当前轨道，如果没有其他存货的轨道，则连同源一起结束
 *        getCapabilities: {MediaTrackCapabilities Function()}，返回当前轨道的源的能力集合
 *        getConstraints: {MediaTrackConstraints Function()}，返回当前轨道的约束集合
 *        getSettings: {MediaTrackSetting}，返回当前轨道的设置集合
 *        applyConstraints: {Promise<void> Function([ MediaTrackConstraints constraints ])}，对当前轨道应用指定约束集
 *    }
 * 
 * 
 * MediaTrackSupportedConstraints 接口定义了一组 UA 需要识别的轨道对象的能力。这套字典只能作为某些函数的返回结果，而不能作为操作参数。
 * 未来的某些规格可以通过定义布尔类型的局部成员来扩展 MediaTrackSupportedConstaints 接口。
 *    MediaTrackSupportedConstraints 字典：{
 *        width, height, aspectRatio（宽高比）,frameRate（帧率）,facingMode（面向类型）,
 *        volume（音量）,sampleRate（采样频率）,sampleSize（采样尺寸）,echoCancellation（消除回声）,
 *        autoGainControl（自动增量控制）,noiseSuppression（噪音消除）,latency（等待时间）,
 *        channelCount（通道数量）,deviceId（设备ID）,groupId（分组ID）
 *    }
 * 以上所有字段均为 boolean 类型，其值表示当前 UA 是否支持对应的约束能力。
 * 
 * 
 * MediaTrackCapabilities 接口代表了一个轨道对象的能力集合。未来的某些规格可以通过定义适当类型的成员字段来扩展 MediaTrackCapabilities 接口。
 *    MediaTrackCapabilities 字典：{ 
 *        成员字段与 MediaTrackSupportedConstraints 保持一致，不同点在于各个成员字段的类型不再统一，而是根据实际定义了各种不同类型
 *        width/height/sampleRate/sampleSize/channelCount：这几个字段均是 LongRange 类型
 *        aspectRatio/frameRate/volume/latency：这几个字段均是 DoubleRange 类型
 *        facingMode：是一个 Array<string>，支持的值是特定的枚举类型
 *        echoCancellation/autoGainControl/noiseSuppression: 均是 Array<boolean> 类型
 *        deviceId/groupId：均为 string 类型
 *    }
 * 这里涉及到几个简单的类型接口和枚举类型，这与各字段的含义息息相关：
 *    LongRange 字典：{ max: {long}，当前属性合法的最大值； min: {long}，当前属性合法的最小值 }
 *    DoubleRange 字典：{ max: {double}，当前属性合法的最大值； min: {double}，当前属性合法的最小值 }
 *    enum VideoFacingModeEnum 字典：{ 这是 facingMode 字段的取值范围
 *        user: 前置摄像头， environment: 后置摄像头，left/right：左右摄像头（往往没有）
 *    }
 * 
 * 
 * MediaTrackConstraints 接口继承自 MediaTrackConstraintsSet 接口，并定义一个私有字段 advanced
 *    MediaTrackConstraints : MediaTrackConstraintsSet {
 *        advanced: Array<MediaTrackConstraintsSet>
 *    }
 *    因为 WebIDL 的局限性，接口不能使用这里定义的简单的约束和约束集合子类来实现约束模式。作为代替，接口必须提供符合这一模式的自己的定义
 * 约束集合（ConstraintSet）中每一个成员都符合一个可约束属性，并且被指定为该属性合法能力值的一个子集。
 * 应用约束时，命令 UA 将设置对应可约束属性值的行为限制为只能设置指定值或值范围。一个给定的属性既可能存在于基本约束集合，也可能存在于高级约束集合列表中，
 * 并且，在高级约束列表中每个约束集合内至多只能出现一次。
 *    了解了上面这些，我们才能很好的理解 advanced 属性。advanced 属性是一个 UA 必须尝试满足的约束集合的顺序列表，会跳过那些无法被满足的约束。
 * 这些约束集合的顺序很重要，通常情况下，当它们作为 applyConstraints 函数的参数时，UA 必须按顺序去尝试满足。
 * 因此，如果高级约束列表中约束集合C1 C2 可以被独立满足，但不能同时满足时，往往最后只有第一个约束集合会被满足
 * 默认情况下，UA 必须要尝试去满足列表中所有约束，即便那个约束不能被满足。因此，如果有一个可满足约束集合 C3 在 C1 与 C2 之后，它也会被满足。
 *    因此必须要了解一下 MediaTrackConstraintsSet 字典：{
 *        成员与 MediaTrackSupportedConstraints 一致，但数据类型均改变，以符合约束模式。
 *        width/height/sampleRate/sampleSize/channelCount: {ConstrainLong <=> (long or ConstrainLongRange)}，其中 ConstrainLongRange : LongRange { exact: {long} 属性所需要的确切值, ideal: {long} 属性所需要的理想值}
 *        aspectRatio/frameRate/volume/latency/: {ConstrainDouble <=> (double or ConstrainDoubleRange)}，其中 ConstrainDoubleRange : DoubleRange { exact: {double} 属性需要的确切值， ideal: {double} 属性需要的理想值 }
 *        facingMode/deviceId/groupId: {ConstrainDOMString <=> (string or Array<string> or ConstrainDOMStringParameters)}，其中 ConstrainDOMStringParameters : { exact: {string || Array<string>} 属性需要的确切值, ideal: {string || Array<string>} 属性需要的理想值 }
 *        echoCancellation/autoGainConstrol/noiseSuppression: {ConstrainBoolean <=> (boolean || ConstrainBooleanParameters)}，其中 ConstrainBooleanParameters : { exact: {string || Array<string>} 属性需要的确切值, ideal: {string || Array<string>} 属性需要的理想值 }
 *    }
 * 
 * 
 * MedaiTrackSettings 表示轨道对象的配置对象，未来的某些规格可以通过定义适当类型的成员字段来扩展 MediaTrackSettings 接口。
 *    MediaTrackSettings 字典：{
 *        成员与 MediaTrackSupportedConstraints，但数据类型必然是对应的基础类型，因为是配置信息
 *        width/height/sampleRate/sampleSize/channelCount: {long}
 *        aspectRatio/frameRate/latency/volume: {double}
 *        facingMode/deviceId/groupId: {string}
 *        echoCancellation/autoGainControl/noiseSuppression: {boolean}
 *    }
 * 
 * 
 * 以上种种接口均为 MediaStream API 的具体实现，本接口最简单的入口是本地媒体设备访问，一般通过 MediaDevices 接口。
 * navigator.mediaDevices.enumerateDevices().then((res) => {console.log(res)})
 * 上面的表达式中，res 对象便是 MediaDeviceInfo 实例数组，MediaDeviceInfo 字典：{
 *    deviceId, kind, label, groupId, toJSON()
 * }
 * 如果设备类型是输入设备的话，则还有一个 getCapabilities 方法。
 * 
 * 
 * 获取本地多媒体类容的方法是通过扩展 NavigatorUserMedia 和 MediaDevices API。挂载一个 getUserMedia 方法来获取。
 * MediaDevices 扩展字典：{
 *    getUserMedia: {Promise<MediaStream> Function(MediaStreamConstraints constraints, NavigatorUserMediaSuccessCallback successCallback, NavigatorUserMediaErrorCallback errorCallback)}
 *    getSupportedConstraints: {MediaTrackSupportedConstaints Function()}，获取受轨道支持的约束
 * }
 * 辅助接口 MediaStreamConstraints 字典：{
 *    video: {boolean || MediaStreamConstraints}，如果为布尔类型，则尝试获取视频轨道设备，如果是媒体流约束类型则尝试获取满足约束的设备
 *    audio: {boolean || MediaStreamConstraints}，如果为布尔类型，则尝试获取音频轨道设备，其余同上
 * }
 */

/**
 * 下面是一些综合应用的实例
 */

let clientWidth = document.documentElement.clientWidth;
let clientHeight = document.documentElement.clientHeight;
console.log(clientWidth, clientHeight);

/**
 * 判断是否支持指定约束，如果没有指定则返回所有约束
 * @param {string} constraints 指定约束
 */
const canBeSupportedConstraints = (constraints) => {
  let supports = navigator.mediaDevices.getSupportedConstraints();
  return constraints ? supports[constraints] : supports;
}

/**
 * 获取当前 UA 支持的用户媒体信息
 */
const getDevices = () => {
  return new Promise((resolve, reject) => {
    navigator.mediaDevices.enumerateDevices().then(res => {
      resolve(res);
    }).catch(e => {
      reject(e);
      console.error(`getUserMedia() failed: ${e.message}`);
    })
  })
}

/**
 * 获取处理媒体流
 * @param {MediaStream} mediaStream 媒体流
 */
const getStream = async (mediaStream) => {
  let oVideo = document.querySelector("#curVideo");
  oVideo.srcObject = mediaStream;

  let track = mediaStream.getVideoTracks()[0];
  let capabilities = track.getCapabilities();
  let settings = track.getSettings();
  console.log(capabilities); // 这里有的能力才能被应用，现在可用的还太少
  console.log(settings); // 默认给的配置

  // 翻转按钮点击
  document.querySelector("#flip").addEventListener('click', () => {
    track.applyConstraints({
      advanced: [
        { facingMode: 'environment' }
      ]
    }).then(() => {
      console.log('success');
    })
  }, false);
}

// 获取媒体设备流
navigator.mediaDevices.getUserMedia({
  video: {
    facingMode: ['user', 'environment']
  }
}).then(getStream)
.catch(e => {
  console.error(`getUserMedia() failed: ${e.message}`);
}) 